import 'dart:async';
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import 'package:flutter_blue/flutter_blue.dart';
import 'package:fluttertoast/fluttertoast.dart';

import 'package:ilive_ihome/model/dtype.dart';
import 'package:ilive_ihome/model/data.dart';
import 'package:ilive_ihome/service/auth.dart';

const String UUID_BASE = '00000000-0000-1000-8000-00805F9B34FB';

/* #define NUS_BASE_UUID  {{0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0, 0x93, 0xF3, 0xA3, 0xB5, 0x00, 0x00, 0x40, 0x6E}} /**< Used vendor specific UUID. */*/
const String UUID_NUS = '6E400001-B5A3-F393-E0A9-E50E24DCCA9E';
const String UUID_NUS_RX = '6E400002-B5A3-F393-E0A9-E50E24DCCA9E';
const String UUID_NUS_TX = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E';

const String UUID_IO = '0x1815'; /* '0x1815': 'Automation IO', */
const String UUID_DIGITAL = '0x2A56'; /* '0x2A56': 'Digital', */
const String UUID_SENSOR = '0x181A'; /* '0x181A': 'Environmental Sensing', */

class DevBleManage {
  DevBleManage(
    this.device, {
    this.connectable = true,
    this.isSearch = false,
    this.rssi,
    this.pos,
    this.mtu = 23,
    this.state = DevBleState.idle,
  }) {
    authResult = DevAuthResult();
    count = 0;
    if (mtu <= 23) {
      mtu = 23;
    }
  }

  BluetoothDevice device;
  bool connectable;
  bool isSearch;
  int rssi;
  Alignment pos;
  DevBleState state;
  List<BluetoothService> services;
  DevAuthResult authResult;
  int count;

  int mtu;
  bool stateChange = false;
  String soc;
  String deviceType;
  String hardware;
  String version;

  void connect({
    VoidCallback onConnected,
    Duration timeout = const Duration(seconds: 10),
  }) {
    print('connect ${deviceString()} $state');
    switch (state) {
      case DevBleState.connected:
        if (onConnected != null) {
          onConnected();
        }
        break;
      case DevBleState.connecting:
        break;
      case DevBleState.error:
      case DevBleState.timeout:
      case DevBleState.idle:
      case DevBleState.disconnected:
        count = 0;
        state = DevBleState.connecting;
        device.connect(timeout: timeout, autoConnect: true);
        device.state.listen((sta) {
          print('${deviceString()} $sta');
          stateChange = true;
          switch (sta) {
            case BluetoothDeviceState.connected:
              state = DevBleState.connected;
              authResult = DevAuthResult();
              break;
            case BluetoothDeviceState.connecting:
              state = DevBleState.connecting;
              break;
            case BluetoothDeviceState.disconnected:
            case BluetoothDeviceState.disconnecting:
              authResult = DevAuthResult();
              break;
          }
        }, onError: (err) {
          state = DevBleState.error;
          print('${deviceString()} 连接发生错误, $err');
        });
        break;
    }
  }

  void disconnect() {
    device.disconnect();
    state = DevBleState.idle;
    setAuthState();
  }

  bool isConnected() {
    return state == DevBleState.connected;
  }

  void serviceDiscover() async {
    services = await device.discoverServices();
  }

  void authState(List<int> data) {
    String resp = utf8.decode(data);
    print('authState() $resp');

    int off = resp.indexOf('=');
    if (off >= 0) {
      String value = resp.substring(off + 1);
      switch (resp.substring(0, off)) {
        case 'random':
          setAuthState(
            authResult: DevAuthResult(
              state: DevAuthState.random,
              value: value,
            ),
          );
          break;
        case 'pubkey':
          setAuthState(
            authResult: DevAuthResult(
              state: DevAuthState.pubkey,
              value: value,
            ),
          );
          if (authResult.value.compareTo('success') == 0) {
            setAuthState(state: DevAuthState.success);
          }
          break;
      }
    }
  }

  bool nusWrite(List<int> data) {
    if (services != null) {
      RegExp regExp = RegExp('^${UUID_NUS_RX}\$', caseSensitive: false);
      for (int i = 0; i < services.length; i++) {
        BluetoothService service = services.elementAt(i);
        List<BluetoothCharacteristic> characteristics = service.characteristics;
        for (int j = 0; j < characteristics.length; j++) {
          BluetoothCharacteristic characteristic = characteristics.elementAt(j);
          String uuid = characteristic.uuid.toString();
          if (regExp.hasMatch(uuid)) {
            characteristic.write(data);
            print('nusWrite() ${data}, ${utf8.decode(data)}');
            return true;
          }
        }
      }
    }
    return false;
  }

  bool nusSetNotifyValue({
    void Function(List<int>) notifyValue,
    void Function(List<int>) callback,
  }) {
    if (services != null) {
      RegExp regExp = RegExp('^${UUID_NUS_TX}\$', caseSensitive: false);
      for (int i = 0; i < services.length; i++) {
        BluetoothService service = services.elementAt(i);
        List<BluetoothCharacteristic> characteristics = service.characteristics;
        for (int j = 0; j < characteristics.length; j++) {
          BluetoothCharacteristic characteristic = characteristics.elementAt(j);
          String uuid = characteristic.uuid.toString();
          if (regExp.hasMatch(uuid)) {
            characteristic.setNotifyValue(true);
            Future.delayed(Duration(milliseconds: 500), () {
              characteristic.value.listen((data) {
                print('nusSetNotifyValue() $data');
                authState(data);
                if (authResult.state == DevAuthState.success) {
                  if (notifyValue != null) {
                    notifyValue(data);
                  }
                }
                if (callback != null) {
                  callback(data);
                }
              });
            });
            return true;
          }
        }
      }
    }
    return false;
  }

  void setAuthState({
    DevAuthState state = DevAuthState.unknow,
    DevAuthResult authResult,
  }) {
    if (authResult == null) {
      authResult = DevAuthResult(state: state);
    }
    switch (authResult.state) {
      case DevAuthState.unknow:
      case DevAuthState.randomRequest:
      case DevAuthState.random:
      case DevAuthState.pubkeySend:
      case DevAuthState.pubkey:
      case DevAuthState.error:
      case DevAuthState.success:
        if (this.authResult.state != authResult.state) {
          this.authResult = authResult;
          count = 0;

          if (mtu != 23 && authResult.state == DevAuthState.success) {
            device.requestMtu(mtu);
          }
        } else {
          this.authResult.value = authResult.value;
        }
        break;
    }

    if (authResult.state == DevAuthState.unknow) {
      count = 0;
    }
  }

  String deviceString() {
    return '${device.name}:${device.id.id}';
  }

  String getCmd() {
    print('deviceType: $deviceType');
    if (deviceType == null) {
      return null;
    }

    if (deviceType.compareTo('ble.lamp') == 0) {
      return 'lamp';
    }
    return null;
  }
}

/*
* Battery Service: 0x180F
* Automation IO: 0x1815
* Current Time Service: 0x1805
* Tx Power: Tx Power
* Environmental Sensing: 0x181A
* Device Information: 0x180A
* User Data: 0x181C
* Alert Notification Service: 0x1811
*
* Analog: 0x2A58
* Analog Output: 0x2A59
* Battery Level: 0x2A19
* Battery Level State: 0x2A1B
* Current Time: 0x2A2B
* Date Time: 0x2A08
* Device Name: 0x2A00
* Digital: 0x2A56
* Digital Output: 0x2A57
* Humidity: 0x2A6F
* Pressure: 0x2A6D
* Rainfall: 0x2A78
* Temperature: 0x2A6E
* Tx Power Level: 0x2A07
*
* */
class DevBle {
  bool _inited = false;

  FlutterBlue _instance = FlutterBlue.instance;
  BluetoothState state = BluetoothState.unknown;
  Map<String, DevBleManage> devices = Map();

  bool _scanning = false;
  void Function(ScanResult) _onData;
  Function _onError;
  void Function() _onDone;
  bool _cancelOnError;
  Duration _timeout;

  void init() async {
    if (_inited == true) {
      return;
    }
    _inited = true;
  }

  bool NavigatorPush(BuildContext context, String id,
      {bool replace = false, bool add = false}) {
    /* todo: implement  ble.NavigatorPush */
    DevBleManage manage = devices[id];
    if (manage?.services != null) {
      if (_uuidMatch(manage.services, UUID_IO)) {
        ILiveDev.NavigatorPush(
          context,
          ILiveDevType.io,
          id.toString(),
          replace: replace,
          add: add,
        );
        return true;
      }
    }
    return false;
  }

  void scan(
    void Function(ScanResult) onData, {
    Function onError,
    void onDone(),
    bool cancelOnError = true,
    Duration timeout,
  }) {
    if (_scanning == false) {
      _scanning = true;
      _onData = onData;
      _onError = onError;
      _onDone = onDone;
      _cancelOnError = cancelOnError;
      _timeout = timeout;
      print('ble scan');
      _instance.scan(timeout: timeout).listen((result) {
        String id = result.device.id.id;
        DevBleManage manage = devices[id];
        if (manage == null) {
          devices[id] = DevBleManage(
            result.device,
            connectable: result.advertisementData.connectable,
            isSearch: true,
            rssi: result.rssi,
          );
        } else {
          manage.connectable = result.advertisementData.connectable;
          manage.rssi = result.rssi;
        }

        if (onData != null) {
          onData(result);
        }

        /* todo: disable debug information */
        print('${result.device.name}: ${result.device.id.id}');
      }, onError: (err) {
        _scanning = false;
        if (onError != null) {
          onError(err);
        }
      }, onDone: () {
        _scanning = false;
        if (onDone != null) {
          onDone();
        }
      }, cancelOnError: cancelOnError);
    }
  }

  void pause() {
    if (_scanning == true) {
      _instance.stopScan();
      _scanning = false;
      print('ble pause');
    }
  }

  void resume() {
    if (_scanning == false && _onData != null) {
      scan(
        _onData,
        onError: _onError,
        onDone: _onDone,
        cancelOnError: _cancelOnError,
        timeout: _timeout,
      );
      print('ble resume');
    }
  }

  void stop() {
    if (_scanning == true) {
      _scanning = false;
      _instance.stopScan();
    }

    _onData = null;
    _onError = null;
    _onDone = null;
    _cancelOnError = null;
    print('ble stop');
  }

  bool isScanning() {
    return _scanning;
  }

  void clear() {
    Map<String, DevBleManage> active = Map();
    ble.devices.forEach((id, manage) {
      if (manage.state == DevBleState.connected ||
          manage.state == DevBleState.connecting ||
          manage.isSearch == false) {
        active[id] = manage;
      }
    });
    ble.devices = active;
  }

  bool authRandomRequst(String id) {
    DevBleManage manage = devices[id];
    if (manage?.services != null) {
      manage.setAuthState(state: DevAuthState.randomRequest);
      print('authRandomRequst()');
      return _authRequstWrite(manage, utf8.encode('random?'));
    }
    return false;
  }

  bool authPubkeySend(String id, int key, int rand) {
    DevBleManage manage = devices[id];
    if (manage?.services != null) {
      manage.setAuthState(state: DevAuthState.pubkeySend);
      String pubkey = 'pubkey=${Auth.pubkey(key, rand).toRadixString(16)}';
      print('authPubkeySend() ${pubkey}');
      return _authRequstWrite(manage, utf8.encode(pubkey));
    }
    return false;
  }

  Future<List<int>> authResponseRead(String id) {
    DevBleManage manage = devices[id];
    if (manage?.services != null && _uuidMatch(manage.services, UUID_NUS_TX)) {
      RegExp regExp = RegExp('^${UUID_NUS_TX}\$', caseSensitive: false);
      for (int i = 0; i < manage.services.length; i++) {
        BluetoothService service = manage.services.elementAt(i);
        List<BluetoothCharacteristic> characteristics = service.characteristics;
        for (int j = 0; j < characteristics.length; j++) {
          BluetoothCharacteristic characteristic = characteristics.elementAt(j);
          String uuid = characteristic.uuid.toString();
          if (regExp.hasMatch(uuid)) {
            print('authResponseRead()');
            return characteristic.read();
          }
        }
      }
    }
    print('authResponseRead() -> null');
    return Future<List<int>>.value(null);
  }

  bool authResponseSetNotifyValue(
    String id, {
    void Function(List<int>) notifyValue,
    void Function(List<int>) callback,
  }) {
    DevBleManage manage = devices[id];
    if (manage?.services != null && _uuidMatch(manage.services, UUID_NUS_TX)) {
      RegExp regExp = RegExp('^${UUID_NUS_TX}\$', caseSensitive: false);
      for (int i = 0; i < manage.services.length; i++) {
        BluetoothService service = manage.services.elementAt(i);
        List<BluetoothCharacteristic> characteristics = service.characteristics;
        for (int j = 0; j < characteristics.length; j++) {
          BluetoothCharacteristic characteristic = characteristics.elementAt(j);
          String uuid = characteristic.uuid.toString();
          if (regExp.hasMatch(uuid)) {
            characteristic.setNotifyValue(true);
            Future.delayed(Duration(milliseconds: 500), () {
              characteristic.value.listen((data) {
                print('authResponseSetNotifyValue() $data');
                manage.authState(data);
                if (manage.authResult.state == DevAuthState.success) {
                  if (notifyValue != null) {
                    notifyValue(data);
                  }
                }
                if (callback != null) {
                  callback(data);
                }
              });
            });
            return true;
          }
        }
      }
    }
    return false;
  }

  BluetoothDevice findDeviceByMac(String mac) {
    return findManageByMac(mac)?.device;
  }

  DevBleManage findManageByMac(String mac) {
    for (int i = 0; i < devices.length; i++) {
      String id = devices.keys.elementAt(i);
      if (id == mac) {
        return devices[id];
      }
    }
    return null;
  }

  bool isConnected(String id) {
    return devices[id]?.state == DevBleState.connected;
  }

  void connect(
    String id, {
    VoidCallback onConnected,
    Duration timeout = const Duration(seconds: 10),
  }) {
    DevBleManage manage = devices[id];
    if (manage != null) {
      manage.connect(onConnected: onConnected, timeout: timeout);
    } else {
      Fluttertoast.showToast(msg: '连接失败：找不到设备');
      print('连接失败：找不到设备');
      return;
    }
  }

  void disconnect(String id) {
    DevBleManage manage = devices[id];
    if (manage != null) {
      manage.disconnect();
    }
  }

  void serviceDiscover(String id) {
    DevBleManage manage = devices[id];
    if (manage == null) {
      Fluttertoast.showToast(msg: '错误：找不到设备');
      print('错误：找不到设备');
      return;
    }

    manage.serviceDiscover();
  }

  bool remove(String id) {
    if (id == null) {
      return false;
    }

    ble.devices.remove(id);
    return true;
  }

  static String short_uuid(String uuid128) {
    RegExp regex = RegExp('^0000[0-9A-F]{4}-0000-1000-8000-00805F9B34FB\$',
        caseSensitive: false);
    if (regex.hasMatch(uuid128)) {
      return '0x${uuid128.substring(4, 8).toUpperCase()}';
    }
    regex = RegExp('^[0-9A-F]{8}-0000-1000-8000-00805F9B34FB\$',
        caseSensitive: false);
    if (regex.hasMatch(uuid128)) {
      return '0x${uuid128.substring(4, 8).toUpperCase()}';
    }
    return uuid128;
  }

  static String gatt_service(String uuid) {
    RegExp regex = RegExp('^0x[0-9A-F]{4}\$', caseSensitive: false);
    if (regex.hasMatch(uuid)) {
      String gatt = GATTService['0x${uuid.substring(2, 6).toUpperCase()}'];
      if (gatt != null) {
        return gatt;
      }
    }
    return 'N/A';
  }

  static String gatt_characteristics(String uuid) {
    RegExp regex = RegExp('^0x[0-9A-F]{4}\$', caseSensitive: false);
    if (regex.hasMatch(uuid)) {
      String gatt =
          GATTCharacteristics['0x${uuid.substring(2, 6).toUpperCase()}'];
      if (gatt != null) {
        return gatt;
      }
    }
    return 'N/A';
  }

  bool _nusMatch(List<BluetoothService> services) {
    RegExp regex = RegExp('^6E40[0-9A-F]{4}-B5A3-F393-E0A9-E50E24DCCA9E\$',
        caseSensitive: false);

    for (int i = 0; i < services.length; i++) {
      BluetoothService service = services.elementAt(i);
      String uuid = service.uuid.toString();
      print('serivce: ${gatt_service(uuid)}: ${uuid}');
      if (regex.hasMatch(uuid)) {
        return true;
      }

      List<BluetoothCharacteristic> characteristics = service.characteristics;
      for (int j = 0; j < characteristics.length; j++) {
        BluetoothCharacteristic characteristic = characteristics.elementAt(j);
        uuid = characteristic.uuid.toString();
        if (regex.hasMatch(uuid)) {
          return true;
        }
      }
    }
    return false;
  }

  bool _uuidMatch(List<BluetoothService> services, String uuid) {
    RegExp regExp = RegExp('^${uuid}\$', caseSensitive: false);
    for (int i = 0; i < services.length; i++) {
      BluetoothService service = services.elementAt(i);
      String _uuid = short_uuid(service.uuid.toString());
      print('serivce: ${gatt_service(_uuid)}: ${_uuid}');
      if (regExp.hasMatch(_uuid)) {
        print('matched: ${uuid}');
        return true;
      }

      List<BluetoothCharacteristic> characteristics = service.characteristics;
      for (int j = 0; j < characteristics.length; j++) {
        BluetoothCharacteristic characteristic = characteristics.elementAt(j);
        _uuid = short_uuid(characteristic.uuid.toString());
        if (regExp.hasMatch(_uuid)) {
          print('matched: ${uuid}');
          return true;
        }
      }
    }
    print('match fail: ${uuid}');
    return false;
  }

  bool _authRequstWrite(DevBleManage manage, List<int> data) {
    if (manage?.services != null && _uuidMatch(manage.services, UUID_NUS_RX)) {
      RegExp regExp = RegExp('^${UUID_NUS_RX}\$', caseSensitive: false);
      for (int i = 0; i < manage.services.length; i++) {
        BluetoothService service = manage.services.elementAt(i);
        List<BluetoothCharacteristic> characteristics = service.characteristics;
        for (int j = 0; j < characteristics.length; j++) {
          BluetoothCharacteristic characteristic = characteristics.elementAt(j);
          String uuid = characteristic.uuid.toString();
          if (regExp.hasMatch(uuid)) {
            characteristic.write(data);
            print('_authRequstWrite() ${data}, ${utf8.decode(data)}');
            return true;
          }
        }
      }
    }
    return false;
  }
}
